Skip to content

Enable background mode lifecycle#4

Closed
Newarr wants to merge 12 commits intofeature/menu-bar-status-itemfrom
feature/background-mode-lifecycle
Closed

Enable background mode lifecycle#4
Newarr wants to merge 12 commits intofeature/menu-bar-status-itemfrom
feature/background-mode-lifecycle

Conversation

@Newarr
Copy link
Copy Markdown
Owner

@Newarr Newarr commented Mar 20, 2026

Problem

OpenOats shows a dock icon and requires a visible window to function. For a meeting transcription app that should listen for calls in the background, this means unnecessary desktop clutter. Users must keep the app window open or lose functionality.

Solution

Wire the menu bar components (from PR #3) into the app lifecycle and enable true background mode:

LSUIElement

Info.plist sets LSUIElement=YES -- the app never shows a dock icon. This is the standard pattern used by Raycast, CleanShot X, Alfred, and Rectangle.

Window close interception

Closing the main window calls orderOut(nil) instead of destroying it. The window stays in memory for instant re-show via the menu bar popover's "Open OpenOats" button. NSApp.activate(ignoringOtherApps: true) ensures the window takes focus.

Termination guard

Quitting while recording shows an NSAlert:

  • "Stop & Quit" -- finalizes the session (awaits coordinator reaching .idle state, 30s timeout), then terminates
  • "Cancel" -- resumes recording

Uses NSApplication.TerminateReply.terminateLater with proper reply(toApplicationShouldTerminate:) instead of a hardcoded delay.

One-shot notification

First time the user closes the main window with auto-detect enabled, a macOS notification explains: "OpenOats is still running. Meeting detection is active." Shown once, tracked via UserDefaults. Requests notification authorization before sending.

Changed files

File Change
Info.plist Add LSUIElement = YES
OpenOatsApp.swift Wire MenuBarController in AppDelegate, window close interception, termination guard, background notification, showMainWindow helper

Test plan

  • App launches with no dock icon
  • Menu bar icon is visible
  • Close main window -- window hides, app stays running
  • Click "Open OpenOats" in popover -- window reappears and takes focus
  • First window close with auto-detect on -- "still running" notification appears
  • Second window close -- no notification (one-shot)
  • Quit while recording -- confirmation alert appears
  • "Stop & Quit" -- session finalizes completely before process exits
  • "Cancel" -- recording continues
  • Quit while idle -- exits immediately, no alert
  • Close window while recording -- recording continues, menu bar shows filled icon

PR 4 of 4 in the menu bar background mode series.
Depends on: #3 Add menu bar status item and popover

@Newarr Newarr force-pushed the feature/menu-bar-status-item branch from c11552e to 07b634e Compare March 20, 2026 18:48
@Newarr Newarr force-pushed the feature/background-mode-lifecycle branch from 5283cc3 to 3cb882f Compare March 20, 2026 18:48
Set LSUIElement=YES so the app runs without a dock icon. Wire
MenuBarController into AppDelegate, intercept window close to hide
instead of destroy (orderOut), add termination guard that awaits
session finalization (30s timeout) before quitting, and show a
one-shot notification explaining background mode on first window
hide with auto-detect enabled.

Window identification uses a static constant (mainWindowID) with
exact equality matching instead of substring search.
@Newarr Newarr force-pushed the feature/background-mode-lifecycle branch from 3cb882f to f4b4822 Compare March 20, 2026 19:01
@Newarr Newarr force-pushed the feature/menu-bar-status-item branch from 07b634e to 7c582f4 Compare March 20, 2026 19:01
yazins-ai and others added 11 commits March 20, 2026 22:13
Add acoustic echo cancellation via Apple Voice Processing. Enables setVoiceProcessingEnabled on AVAudioEngine mic input to cancel speaker echo that causes duplicate transcription. On by default with toggle in Settings > Recording. Closes yazinsai#88
Redesign NotesView: 2-tab model with batch cleanup

Replaces the 3-state Raw/Refined/Notes detail view with a clean 2-tab segmented picker (Transcript / Notes). Adds batch transcript cleanup via TranscriptCleanupEngine for on-demand refinement of past sessions.

Based on the design direction from @Newarr in PRs yazinsai#82 and yazinsai#83 — thank you for the original work! Reimplemented to keep drain() timeout at 5s and strip unrelated scope changes.

Closes yazinsai#82, closes yazinsai#83.
Add structured Markdown meeting output (openoats/v1)

Produces `.md` files in ~/Documents/OpenOats/ alongside existing `.txt` output.
YAML frontmatter with date, duration, participants, ASR engine, and meeting app.
Body sections: Summary, Action Items, Decisions, Transcript (high-signal first).
Speaker-attributed transcript lines with relative timestamps.

Three processing stages: raw transcript (always written at finalization),
LLM-enriched sections inserted when notes are generated. Existing .txt
output is preserved unchanged.

Includes format specification, example transcript, and 42 unit tests.

Closes no issues — new capability.
Derive isRecording from coordinator state machine

Replace writable stored `_isRecording` property with a computed property
that reads directly from the coordinator's authoritative `MeetingState`.
Eliminates stale-value risk from the 100ms polling loop.

Also adds `MeetingMetadata.manual()` static factory to deduplicate inline
metadata construction across call sites.
Extract service initialization for headless operation

Add ensureServicesInitialized() on AppRuntime with an idempotency guard.
Coordinator-owned services (TranscriptionEngine, TranscriptLogger,
RefinementEngine, AudioRecorder) are created on first call from wherever
runs first. ContentView.task now only creates view-local services
(KnowledgeBase, SuggestionEngine) that don't need to outlive the window.
…ement

LSUIElement=YES makes the app a permanent accessory with no menu bar
or dock icon. Toggle to .regular on launch and window show, .accessory
on window close. Standard pattern used by Bartender, MonitorControl, etc.
Add clearModelCache() to TranscriptionBackend protocol so corrupt model
files are removed on load failure. Each backend (Parakeet, Qwen3,
WhisperKit) implements targeted cache deletion. TranscriptionEngine now
clears cache and resets download state on failure, so "Download Now"
triggers a fresh download.
Add horizontal wordmark logo (icon + text) for marketing and documentation use. Contributed by @Alex-Wengg.
…yazinsai#95) (yazinsai#103)

Fix mic tap format failure and AEC conflict with system audio capture. Based on analysis from @brandonbloom in yazinsai#95.
* Derive isRecording from coordinator state machine

isRecording was a writable stored property set by ContentView's 100ms
polling loop. Any consumer outside that loop saw stale values when
the main window was hidden. Replace with a computed property that
reads directly from the coordinator's authoritative MeetingState.

Also add MeetingMetadata.manual() factory to eliminate duplicate
inline construction of manual session metadata.

* Extract service initialization for headless operation

TranscriptionEngine and other coordinator-owned services were created
inside ContentView.task, making background recording impossible without
a visible window. Add ensureServicesInitialized() on AppRuntime so
services can be created early from AppDelegate. ContentView.task now
only creates view-local services (KnowledgeBase, SuggestionEngine).

* Add menu bar status item and popover

NSStatusItem with two icon states: waveform.circle (idle) and
waveform.circle.fill (recording). Icon updates reactively via
withObservationTracking (zero wakeups when idle).

Popover shows live recording status with elapsed timer computed
from actual session start time, Start/Stop button with consent
gate, Show App, and Quit. Uses NSPopover with NSHostingController
for reliable sizing and focus behavior.

---------

Co-authored-by: Szymon Sypniewicz <szymonsypniewicz@szymons-mac.mynet>
Co-authored-by: Yazin's AI <github@mrfwd.com>
Newarr pushed a commit that referenced this pull request Mar 22, 2026


- Make cancel() async and await task completion to prevent data races (#1)
- Move session-switch guard before backfillRefinedText to prevent stale writes (#3)
- Cancel cleanup engine in teardownMeetingDetection (#4)
- Add .pre-llm.md backup before overwriting Markdown with LLM sections (yazinsai#10)
- Add safety documentation for nonisolated(unsafe) vars (#2)
- Add unit tests for chunkRecords and parseResponse (#5)

https://claude.ai/code/session_01QTKjQDQpoVqFrPJM8bp218
@Newarr Newarr closed this Mar 30, 2026
@Newarr Newarr deleted the feature/background-mode-lifecycle branch March 30, 2026 11:41
@Newarr Newarr mentioned this pull request Mar 21, 2026
9 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants